// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/blink/cdm_session_adapter.h"

#include <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "media/base/cdm_factory.h"
#include "media/base/cdm_key_information.h"
#include "media/base/cdm_promise.h"
#include "media/base/key_systems.h"
#include "media/blink/webcontentdecryptionmodule_impl.h"
#include "media/blink/webcontentdecryptionmodulesession_impl.h"
#include "url/gurl.h"

namespace media {

namespace {
    const char kMediaEME[] = "Media.EME.";
    const char kDot[] = ".";
    const char kTimeToCreateCdmUMAName[] = "CreateCdmTime";
} // namespace

CdmSessionAdapter::CdmSessionAdapter()
    : trace_id_(0)
    , weak_ptr_factory_(this)
{
}

CdmSessionAdapter::~CdmSessionAdapter() { }

void CdmSessionAdapter::CreateCdm(
    CdmFactory* cdm_factory,
    const std::string& key_system,
    const GURL& security_origin,
    const CdmConfig& cdm_config,
    std::unique_ptr<blink::WebContentDecryptionModuleResult> result)
{
    TRACE_EVENT_ASYNC_BEGIN0("media", "CdmSessionAdapter::CreateCdm",
        ++trace_id_);

    base::TimeTicks start_time = base::TimeTicks::Now();

    // Note: WebContentDecryptionModuleImpl::Create() calls this method without
    // holding a reference to the CdmSessionAdapter. Bind OnCdmCreated() with
    // |this| instead of |weak_this| to prevent |this| from being destructed.
    base::WeakPtr<CdmSessionAdapter> weak_this = weak_ptr_factory_.GetWeakPtr();

    DCHECK(!cdm_created_result_);
    cdm_created_result_ = std::move(result);

    cdm_factory->Create(
        key_system, security_origin, cdm_config,
        base::Bind(&CdmSessionAdapter::OnSessionMessage, weak_this),
        base::Bind(&CdmSessionAdapter::OnSessionClosed, weak_this),
        base::Bind(&CdmSessionAdapter::OnSessionKeysChange, weak_this),
        base::Bind(&CdmSessionAdapter::OnSessionExpirationUpdate, weak_this),
        base::Bind(&CdmSessionAdapter::OnCdmCreated, this, key_system,
            start_time));
}

void CdmSessionAdapter::SetServerCertificate(
    const std::vector<uint8_t>& certificate,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    cdm_->SetServerCertificate(certificate, std::move(promise));
}

WebContentDecryptionModuleSessionImpl* CdmSessionAdapter::CreateSession()
{
    return new WebContentDecryptionModuleSessionImpl(this);
}

bool CdmSessionAdapter::RegisterSession(
    const std::string& session_id,
    base::WeakPtr<WebContentDecryptionModuleSessionImpl> session)
{
    // If this session ID is already registered, don't register it again.
    if (base::ContainsKey(sessions_, session_id))
        return false;

    sessions_[session_id] = session;
    return true;
}

void CdmSessionAdapter::UnregisterSession(const std::string& session_id)
{
    DCHECK(base::ContainsKey(sessions_, session_id));
    sessions_.erase(session_id);
}

void CdmSessionAdapter::InitializeNewSession(
    EmeInitDataType init_data_type,
    const std::vector<uint8_t>& init_data,
    CdmSessionType session_type,
    std::unique_ptr<NewSessionCdmPromise> promise)
{
    cdm_->CreateSessionAndGenerateRequest(session_type, init_data_type, init_data,
        std::move(promise));
}

void CdmSessionAdapter::LoadSession(
    CdmSessionType session_type,
    const std::string& session_id,
    std::unique_ptr<NewSessionCdmPromise> promise)
{
    cdm_->LoadSession(session_type, session_id, std::move(promise));
}

void CdmSessionAdapter::UpdateSession(
    const std::string& session_id,
    const std::vector<uint8_t>& response,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    cdm_->UpdateSession(session_id, response, std::move(promise));
}

void CdmSessionAdapter::CloseSession(
    const std::string& session_id,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    cdm_->CloseSession(session_id, std::move(promise));
}

void CdmSessionAdapter::RemoveSession(
    const std::string& session_id,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    cdm_->RemoveSession(session_id, std::move(promise));
}

scoped_refptr<ContentDecryptionModule> CdmSessionAdapter::GetCdm()
{
    return cdm_;
}

const std::string& CdmSessionAdapter::GetKeySystem() const
{
    return key_system_;
}

const std::string& CdmSessionAdapter::GetKeySystemUMAPrefix() const
{
    DCHECK(!key_system_uma_prefix_.empty());
    return key_system_uma_prefix_;
}

void CdmSessionAdapter::OnCdmCreated(
    const std::string& key_system,
    base::TimeTicks start_time,
    const scoped_refptr<ContentDecryptionModule>& cdm,
    const std::string& error_message)
{
    DVLOG(2) << __func__ << ": "
             << (cdm ? "success" : "failure (" + error_message + ")");
    DCHECK(!cdm_);

    TRACE_EVENT_ASYNC_END2("media", "CdmSessionAdapter::CreateCdm", trace_id_,
        "success", (cdm ? "true" : "false"), "error_message",
        error_message);

    if (!cdm) {
        cdm_created_result_->completeWithError(
            blink::WebContentDecryptionModuleExceptionNotSupportedError, 0,
            blink::WebString::fromUTF8(error_message));
        cdm_created_result_.reset();
        return;
    }

    key_system_ = key_system;
    key_system_uma_prefix_ = kMediaEME + GetKeySystemNameForUMA(key_system) + kDot;

    // Only report time for successful CDM creation.
    ReportTimeToCreateCdmUMA(base::TimeTicks::Now() - start_time);

    cdm_ = cdm;

    cdm_created_result_->completeWithContentDecryptionModule(
        new WebContentDecryptionModuleImpl(this));
    cdm_created_result_.reset();
}

void CdmSessionAdapter::OnSessionMessage(
    const std::string& session_id,
    ContentDecryptionModule::MessageType message_type,
    const std::vector<uint8_t>& message)
{
    WebContentDecryptionModuleSessionImpl* session = GetSession(session_id);
    DLOG_IF(WARNING, !session) << __func__ << " for unknown session "
                               << session_id;
    if (session)
        session->OnSessionMessage(message_type, message);
}

void CdmSessionAdapter::OnSessionKeysChange(const std::string& session_id,
    bool has_additional_usable_key,
    CdmKeysInfo keys_info)
{
    WebContentDecryptionModuleSessionImpl* session = GetSession(session_id);
    DLOG_IF(WARNING, !session) << __func__ << " for unknown session "
                               << session_id;
    if (session)
        session->OnSessionKeysChange(has_additional_usable_key,
            std::move(keys_info));
}

void CdmSessionAdapter::OnSessionExpirationUpdate(const std::string& session_id,
    base::Time new_expiry_time)
{
    WebContentDecryptionModuleSessionImpl* session = GetSession(session_id);
    DLOG_IF(WARNING, !session) << __func__ << " for unknown session "
                               << session_id;
    if (session)
        session->OnSessionExpirationUpdate(new_expiry_time);
}

void CdmSessionAdapter::OnSessionClosed(const std::string& session_id)
{
    WebContentDecryptionModuleSessionImpl* session = GetSession(session_id);
    DLOG_IF(WARNING, !session) << __func__ << " for unknown session "
                               << session_id;
    if (session)
        session->OnSessionClosed();
}

WebContentDecryptionModuleSessionImpl* CdmSessionAdapter::GetSession(
    const std::string& session_id)
{
    // Since session objects may get garbage collected, it is possible that there
    // are events coming back from the CDM and the session has been unregistered.
    // We can not tell if the CDM is firing events at sessions that never existed.
    SessionMap::iterator session = sessions_.find(session_id);
    return (session != sessions_.end()) ? session->second.get() : NULL;
}

void CdmSessionAdapter::ReportTimeToCreateCdmUMA(base::TimeDelta time) const
{
    // Note: This leaks memory, which is expected behavior.
    base::HistogramBase* histogram = base::Histogram::FactoryTimeGet(
        GetKeySystemUMAPrefix() + kTimeToCreateCdmUMAName,
        base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromSeconds(10),
        50, base::HistogramBase::kUmaTargetedHistogramFlag);

    histogram->AddTime(time);
}

} // namespace media
